//渲染指令
let CompilerUtil = {
    getvalue (vm, value) {//优化,去获取data的值
        data = vm.$data;
        let data2;
        value.split('.').forEach((ele, item) => {
            data2 = data[ele];//最终获取的值

            data = data[ele];//例time   1.data[time] --> 2.data[time][h] 就是不断的向后面遍历值，拿到最后的值    
        });
        return data2;
    },
    getContent (vm, value) {
        //在这里你所需要考虑的和函数getvalue一样，可能会出现<p>{{name}}-{{age}}</p>这种情况，那么你就需要遍历以及替换
        let reg = /\{\{(.+?)\}\}/gi;
        let val = value.replace(reg, (...args) => {

            return this.getvalue(vm, args[1])
        })
        return val;
    },
    model: function (node, value, vm) {
        //需要三个参数--->1.此时元素节点,2.v-model的值,3.此时的this
        //node.value = vm.$data[value];
        let val = this.getvalue(vm, value)
        node.value = val
    },

    html: function (node, value, vm) {
        let val = this.getvalue(vm, value)
        node.innerHTML = val
    },
    text: function (node, value, vm) {
        let val = this.getvalue(vm, value)
        node.innerText = val
    },
    content: function (node, value, vm) {
        //处理模板与指令类似，都是一个套路
        let val = this.getContent(vm, value)
        //在这里你别忘了，此时的node位置是{{name}}-{{age}}是这个对应的文本而不是一个元素
        //innertext所使用对象是dom元素
        //textContent所使用的对象是节点
        //在这里你不能使用innertext进行替换
        node.textContent = val
    }
};

//创建nue类
class Nue {
    constructor(options) {
        //进行初始化---->保存传过来的el和data
        //因为el可以是id名也可以是你选择的dom元素,所以这里需要判断此时传来的el是否是dom元素
        if (this.isElement(options.el)) {
            //如果是一个元素就直接保存
            this.$el = options.el;
        } else {
            //如果不是呢
            this.$el = document.querySelector(options.el);
        }
        //保存data数据
        this.$data = options.data;
        //现在el和data都保存了,所以你需要开始根据el和data编译渲染页面
        /* 
      思考渲染页面的方式-->我们需要找到v-model,
      找到后获取v-model的值name
      然后向下寻找这样格式的{{name}}的元素,
      将data中的对应的值替换{{name}}

      现在就有一个问题怎么找?
      脑袋第一个思维就是遍历每一个元素,
      但是这样会有一个问题就是,只要你的name的值在不断变换,那么你就需要不断寻找并且编译,重复多次,极其消耗性能

      其实有这样一个案例,你需要利用appendchild去添加子元素,此时你需要添加10000000个,如果你仅仅不断的用appendchild,会导致我加一个元素,我需要将全部重新渲染一遍,其实是不需要的,你可以利用string去将那么多的子元素连在一起,只需要用一次appendchild

      这个思路也可以运用到上面,就是我先将元素全部取出来,存进内存中,再去根据函数改变,最后渲染一次

      步骤:
      1.获取此时绑定的区域 存进内存中
      2.寻找v-modelhe{{}}符号
      3.将数据渲染到对应的标签中
    */
        if (this.$el) {
            new Observer(this.$data);
            new Compiler(this); //由于编译渲染的操作麻烦,就选择class方便管理
        }
    }
    isElement (
        node //判断node是否是一个元素
    ) {
        return node.nodeType == 1;
    }
}

class Compiler {
    constructor(vm) {
        this.vm = vm;
        //将元素放进内存中
        let fragment = this.node2fragment(this.vm.$el); //传入的数据要根据现在this

        //查找指令和和模板
        this.buildTemplate(fragment);
        //渲染编译
        this.vm.$el.appendChild(fragment);
    }
    node2fragment (app) {
        //功能:创建文档碎片,元素放入文档内,进行操作
        //创建一个文档碎片
        let fragment = document.createDocumentFragment();
        //将每个元素放入文档碎片中
        let node = app.firstChild;
        while (node) {
            fragment.appendChild(node);
            node = app.firstChild;
        }
        return fragment;
    }
    //寻找指令和模板
    buildTemplate (fragment) {
        let nodelist = [...fragment.childNodes]; //获取元素,放在数组中


        //进行循环判断
        nodelist.forEach(node => {
            /*
        现在获得的区域的只要在<div id="app"></div>里面都是哪怕是空格,具体解释文档有
        现在就是判断你当前获得的是元素还是文本
        如果是元素,我们就需要判断有没有v-model这样属性
        如果是文本,我们就要判断,有没有{{}}这样的符号
        */
            if (this.vm.isElement(node)) {
                this.buildElement(node);

                this.buildTemplate(node); //为什么会需要再进行一次
            } else {
                this.buildText(node);

            }
        });
    }
    buildElement (node) //元素操作
    {
        let attrs = [...node.attributes];
        //获取元素里面属性
        attrs.forEach(attr => {
            let { name, value } = attr;
            if (name.startsWith('v-')) {
                //
                let [_, directive] = name.split('-'); //找到model
                CompilerUtil[directive](node, value, this.vm);
            }
        });
    }
    buildText (node) //文本操作
    {
        let content = node.textContent; //获取此时文本的数据
        let reg = /\{\{.+?\}\}/gi; //正则表达式
        if (reg.test(content)) {


            CompilerUtil['content'](node, content, this.vm)
        }
    }
}

class Observer {
    // 只要将需要监听的那个对象传递给Observer这个类
    // 这个类就可以快速的给传入的对象的所有属性都添加get/set方法
    constructor(data) {
        this.observer(data);
    }
    observer (obj) {
        if (obj && typeof obj === 'object') {
            // 遍历取出传入对象的所有属性, 给遍历到的属性都增加get/set方法
            for (let key in obj) {
                this.defineRecative(obj, key, obj[key])
            }
        }
    }
    // obj: 需要操作的对象
    // attr: 需要新增get/set方法的属性
    // value: 需要新增get/set方法属性的取值
    defineRecative (obj, attr, value) {
        // 如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
        this.observer(value);
        Object.defineProperty(obj, attr, {
            get () {
                return value;
            },
            set: (newValue) => {
                if (value !== newValue) {
                    // 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
                    this.observer(newValue);
                    value = newValue;
                    console.log('监听到数据的变化, 需要去更新UI');
                }
            }
        })
    }
}